﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Ionic.Zip;
using RoccaStore.Data;
using RoccaStore.Services;
using RoccaStore.Extensions;
using RoccaStore.Upload;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;

namespace RoccaStore.Services
{
    public class FileInfoService : ServiceBase<IFileInfoRepository>, IFileInfoService
    {
        private readonly ISlugService _slugService;
        private readonly IAccountService _accountService;

        public FileInfoService(
            ISlugService slugService,
            IAccountService accountService)
        {
            _slugService = slugService;
            _accountService = accountService;
        }
        public FileInfo GetByID(int id)
        {
            return Repository.GetByID(id);
        }

        public FileInfo GetBySlug(string slug)
        {
            return Repository.GetBySlug(slug);
        }

        public List<FileInfo> GetByFileSendID(int fileSendId)
        {
            return Repository.GetByFileSendID(fileSendId);
        }

        public int Save(FileInfo fileInfo)
        {
            fileInfo.UpdateFileType();

            if (fileInfo.ID == 0)
            {
                if (HttpContext.Current != null)
                {
                    if (string.IsNullOrEmpty(fileInfo.IP))
                        fileInfo.IP = HttpContext.Current.Request.UserHostAddress;

                    fileInfo.AccountID = _accountService.Current != null ? _accountService.Current.ID : default(int?);
                }
            }

            return Repository.Save(fileInfo);
        }

        public int Compress(int slugId)
        {
            Slug slug = _slugService.GetByID(slugId);

            FileInfo file = new FileInfo()
            {
                CreatedDate = DateTime.Now
            };

            Save(file);

            string zipFilename = HttpContext.Current.Server.MapPath(Path.Combine("/storage", file.ID + ".zip"));

            using (ZipFile zip = new ZipFile())
            {
                foreach (var item in slug.Files)
                {
                    FileInfo slugFile = GetByID(item.FileID.Value);
                    string slugFilename = HttpContext.Current.Server.MapPath(Path.Combine("/storage", slugFile.ID + slugFile.Extension));
                    zip.AddEntry(slugFile.Filename, File.ReadAllBytes(slugFilename));
                }

                zip.Save(zipFilename);
            }

            file.Type = Enums.FileType.Compressed;
            file.Filename = slug.Title.MakeSafeFilename() + ".zip";
            file.Extension = ".zip";
            file.CreatedDate = DateTime.Now;
            file.Size = new System.IO.FileInfo(zipFilename).Length;

            Save(file);

            return file.ID;
        }

        public FileInfo SaveAs(HttpPostedFileBase fileBase)
        {
            string fileType = Path.GetExtension(fileBase.FileName.ToString()).Substring(1);

            if (CheckFileAllowed(fileType) == true)
            {
                FileInfo fileInfo = new FileInfo()
                {
                    Filename = Path.GetFileName(fileBase.FileName),
                    Extension = Path.GetExtension(fileBase.FileName),
                    Size = fileBase.ContentLength
                };

                Save(fileInfo);

                string relativePath = Path.Combine("/storage/", fileInfo.ID + fileInfo.Extension);
                string savedFilename = HttpContext.Current.Server.MapPath(relativePath);
                fileBase.SaveAs(savedFilename);

                return fileInfo;
            }
            else
            {
                throw new Exception("Invalid file format");
            }
        }

        public bool CheckFileAllowed(string filetype)
        {
            string[] badTypes = { "conf", "txt", "bat", "exe" };
            return (badTypes.Contains(filetype) == true ? false : true);
        }


        public void Upload(string ip, int? accountId, string uploadId, string folder, Uri uri, string filePath)
        {
            Task.Factory.StartNew(() =>
            {
                try
                {
                    object progressLock = new object();

                    Action<UploadProgress> Save = new Action<UploadProgress>(delegate(UploadProgress uploadProgress)
                    {
                        lock (progressLock)
                        {
                            try
                            {
                                using (FileStream fs = new FileStream(Path.Combine(Path.GetDirectoryName(filePath), uploadProgress.ID + ".tmp"), FileMode.OpenOrCreate))
                                {
                                    var bf = new BinaryFormatter();
                                    bf.Serialize(fs, uploadProgress);
                                }
                            }
                            catch (Exception)
                            {                               
                                throw;
                            }
                        }
                    });

                    WebClient client = new WebClient();
                    client.DownloadProgressChanged += (s, e) =>
                    {
                        var uploadProgress = new UploadProgress()
                        {
                            ID = uploadId,
                            Progress = e.ProgressPercentage,
                            Recieved = e.BytesReceived,
                            Total = e.TotalBytesToReceive
                        };

                        Save(uploadProgress);
                    };
                    client.DownloadFileCompleted += (s, e) =>
                    {
                        // get the filename from the dynamic userstate data
                        string fileType = Path.GetExtension(filePath.ToString()).Substring(1);

                        FileInfo fileInfo;

                        using (FileStream fs = new FileStream(filePath, FileMode.Open))
                        {
                            fileInfo = new FileInfo()
                            {
                                Filename = Path.GetFileName(filePath),
                                Extension = Path.GetExtension(filePath),
                                Size = (int)fs.Length,
                                IP = ip,
                                AccountID = accountId
                            };

                            this.Save(fileInfo);
                        }

                        Slug slug = _slugService.CreateSlug(fileInfo.Filename, fileInfo.ID);

                        // file already exists, so to reduce processing just move (rename) the file
                        if (CheckFileAllowed(fileType) == true)
                        {
                            System.IO.File.Move(filePath, filePath.Replace("\\" + uploadId, string.Empty).Replace(fileInfo.Filename, fileInfo.ID + fileInfo.Extension));
                            //Directory.Delete(folder, true);
                        }

                        UploadProgress uploadProgress = GetUploadProgress(Path.GetDirectoryName(filePath), uploadId);
                        uploadProgress.Slug = slug.Name;
                        uploadProgress.Hash = slug.Key;
                        Save(uploadProgress);
                    };

                    client.DownloadFileAsync(uri, filePath, new { filePath = filePath, uploadId = uploadId });
                }
                catch (Exception)
                {
                    throw;
                }
            });
        }

        public UploadProgress GetUploadProgress(string filePath, string uploadId)
        {
            UploadProgress uploadProgress = new UploadProgress();
            if (System.IO.File.Exists(Path.Combine(filePath, uploadId + ".tmp")))
            {
                using (FileStream fs = new FileStream(Path.Combine(filePath, uploadId + ".tmp"), FileMode.Open, FileAccess.Read))
                {
                    var bf = new BinaryFormatter();
                    uploadProgress = (UploadProgress)bf.Deserialize(fs);
                }
            }
            return uploadProgress;
        }


        public void CleanUp(string basePath)
        {
            // get any temp folders
            foreach (var item in Directory.GetDirectories(basePath))
            {
                if (System.IO.File.GetLastAccessTime(item).AddDays(1) < DateTime.Now)
                {
                    System.IO.Directory.Delete(item, true);
                }
            }
        }
    }
}
